/****************************************************************************
  CLASS:				MiscGaugeCell
  
  For more information, see the interface file.
  
  Since most of the code from this class came from the GaugeView class, here
  is the original comments for your reading pleasure:
   
  GaugeView.m, analog gauge view
  Author: Bruce Blumberg, NeXT Developer Support Group.
  Originally written for 0.6 mid 1988, modified for 1.0 by Ali Ozer.
  Redesigned for 2.0 by Julie Zelenski, NeXT Developer Support
 
  Subclass of view to implement a simple round analog gauge. You can set the 
  minimum, maximum value, start angle, angle range, title, font, and more.  
  It is a pretty generic round gauge view, if you ever have need for one.
 
  You may freely copy, distribute and reuse the code in this example.  
  NeXT disclaims any warranty of any kind, expressed or implied, as to 
  its fitness for any particular use.

  This object is included in the MiscKit by permission from the author
  and its use is governed by the MiscKit license, found in the file
  "LICENSE.rtf" in the MiscKit distribution.  Please refer to that file
  for a list of all applicable permissions and restrictions.
 ****************************************************************************/

#import <appkit/appkit.h>
#import "Gauge.h"				// For the pswraps.
#import "MiscGaugeCell.h"

// Used for class versioning. If you happen to change the ivars, then 
// bump the version up and update the read:/write: methods.
#define MISCGAUGECELL_VERSION 0
#define MISCGAUGECELL_NAME "MiscGaugeCell"


@implementation MiscGaugeCell 

+ initialize
{
	if (self == [MiscGaugeCell class])
		// Set the class's version for archiving purposes.
		[self setVersion: MISCGAUGECELL_VERSION];
	return self;
}


- init
{
	if ([super init] == nil)
		return nil;
	
	cache = [ [NXImage alloc] init];   
	radius = 0.0;
	center.x = center.y = 0.0;
	startAngle = angleRange = degreesPerUnit = 0.0;
	tickInterval = 0;
	gaugeColor = NXConvertGrayToColor (NX_DKGRAY);
	textColor = NXConvertGrayToColor (NX_BLACK);
//	backgroundColor = NXConvertGrayToColor (NX_LTGRAY);
	minValue = 0.0; 
	maxValue = 1.0;
	value = 0.0;
	strValue = NULL;
	tickRatio = 0.65;
	handRatio = 0.6;
    degreesPerUnit = angleRange/(maxValue-minValue);
	[self setType: NX_TEXTCELL];	// Need this so I can use [self font].
	[self setTitle: "Gauge"];
	[self setTitlePosition: NX_ATTOP];
	titleFont = [self font]; 

	return self;
}


- free
{
	if (strValue != NULL)
		NX_FREE (strValue);
	if (cache != nil)	
		cache = [cache free];
	return [super free];
}


- (float)maxValue { return maxValue; }
- (float)minValue { return minValue; }
- (float)floatValue	{ return value; }
- (double)doubleValue { return (double)value; }
- (int)intValue { return (int)value; }


- (const char *)stringValue 
{ 
	if (strValue != NULL)
		NX_FREE (strValue);
	 
	NX_MALLOC (strValue, char, 20);
	sprintf (strValue, "%f", value);
	return strValue;
}


- setMaxValue: (float)max
{
	if ([self maxValue] == max)
		return self;
		
	maxValue = max;
	if ([self minValue] >= [self maxValue])
		[self setMinValue: max-1];
	if ([self floatValue] > maxValue)
		[self setFloatValue: maxValue];

    degreesPerUnit = angleRange/(maxValue-minValue);
	return self;
}


- setMinValue: (float)min
{
	if ([self minValue] == min)
		return self;
		
	minValue = min;
	if ([self minValue] >= [self maxValue])
		[self setMaxValue: min+1];
	if ([self floatValue] < minValue)
		[self setFloatValue: minValue]; 

    degreesPerUnit = angleRange/(maxValue-minValue);
	return self;
}


- setFloatValue: (float)val
{
	if (val < [self minValue])
		val = minValue;
	if (val > [self maxValue])
		val = maxValue;
	value = val;
	return self;
}


- setDoubleValue: (double)val
{
	return [self setFloatValue: (float)val];
}


- setIntValue: (int)val
{
	return [self setFloatValue: (float)val];
}


- setStringValue: (const char *)val
{
	if (val == NULL)
		[self setFloatValue: 0.0];
	else
		[self setDoubleValue: atof(val)];
	return self;
}


//- (NXColor)backgroundColor { return backgroundColor; }
- (NXColor)gaugeColor { return gaugeColor; }
- (NXColor)textColor { return textColor; }

/*
- setBackgroundColor: (NXColor)color
{
	if (NXEqualColor (color, backgroundColor) == NO)
		backgroundColor = color;
	return self;
}
*/

- setGaugeColor: (NXColor)color
{
	if (NXEqualColor (color, gaugeColor) == NO)
		gaugeColor = color;
	return self;
}


- setTextColor: (NXColor)color
{
	if (NXEqualColor (color, textColor) == NO)
		textColor = color;
	return self;
}

/*
- (float)backgroundGray	
{
	float gray;
	NXConvertColorToGray (backgroundColor, &gray);
 	return gray; 
}
*/

- (float)gaugeGray 
{ 
	float gray;
	NXConvertColorToGray (gaugeColor, &gray);
 	return gray; 
}


- (float)textGray 
{ 
	float gray;
	NXConvertColorToGray (textColor, &gray);
 	return gray; 
}

/*
- setBackgroundGray: (float)gray
{
	[self setBackgroundColor: NXConvertGrayToColor(gray)];
	return self;
}
*/

- setGaugeGray: (float)gray
{
	[self setGaugeColor: NXConvertGrayToColor(gray)];
	return self;
}


- setTextGray: (float)gray
{
	[self setTextColor: NXConvertGrayToColor(gray)];
	return self;
}


- (float)startAngle { return startAngle; }
- (float)angleRange	{ return angleRange; }
- (int)tickInterval { return tickInterval; }
- (float)tickRatio { return tickRatio; }
- (float)handRatio { return handRatio; }


- setStartAngle:(float)newValue
{
	// Sets start angle for gauge, which is the angle of the arm when at the 
	// minimum value. needRedraw is set to indicate that face image must be 
	// redrawn. 0 degrees is straight right, with increasing numbers going
	// counter clockwise.
	if (newValue < 0.0)
		newValue = 0.0;
	if (newValue > 360.0)
		newValue = 360.0;
    startAngle = newValue;
    return self;
}


- setAngleRange:(float)newValue
{
	// Sets angle range for gauge, which is the sweep of the arm from minimum
	// value to maximum value.  The value cannot exceed 360 degrees (a full
	// revolution).  Recalculates degreesPerUnit which is used to 
	// determine interval of ticks and labels. needRedraw is set to indicate 
	// that face image must be redrawn.
    if (newValue > 360.0) 
		newValue = 360.0;
	if (newValue < 0.0) 
		newValue = 0.0;
	
    angleRange = newValue;
    degreesPerUnit = angleRange/(maxValue-minValue);
    return self;
}


- setTickInterval:(int)newValue
{
	// Sets tick interval for gauge, which is number of units between tick
	// marks on gauge face.  needRedraw is set to indicate that 
	// face image must be redrawn.
	if (newValue < 1)
		newValue = 1;
    tickInterval = newValue;
    return self;
}


- setTickRatio: (float)newRatio
{
	// Make sure new tick ratio is between 0.0 (no ticks) and 1.0.
	if (newRatio < 0.0)
		newRatio = 0.0;
	if (newRatio > 1.0)
		newRatio = 1.0;
	tickRatio = newRatio;
	return self;
}


- setHandRatio: (float)newRatio
{
	// Make sure new hand ratio is between 0.2 and 0.9.
	if (newRatio < 0.2)
		newRatio = 0.2;
	if (newRatio > 0.9)
		newRatio = 0.9;
	handRatio = newRatio;
	return self;
}


- titleFont { return titleFont; }
- (const char *)title { return [super stringValue]; }
- (int)titlePosition { return titlePosition; }

- setTitleFont: newFont
{
	if ([self titleFont] != newFont)
		titleFont = newFont;
	return self;
}


- setTitle: (const char *)newTitle
{
	// We are making use of the ivar contents inherited from Cell.
	// Since we overrode stringValue and setStringValue:, we have
	// to use Cell's implementation.
	[super setStringValue: newTitle];
	return self;
}	
	

- setTitlePosition: (int)newPos
{
	if (newPos == NX_ATTOP || newPos == NX_ATBOTTOM)
		titlePosition = newPos;
	return self;
}


- calcCellSize: (NXSize *)size inRect:(NXRect *)rect
{
	// Return the minimum size that the cell will fit in.
	size->width = NX_WIDTH(rect);
	size->height = NX_HEIGHT(rect);

	if (size->height < size->width) 
		size->width = size->height;
	else 
		size->height = size->width;

	return self;
}


- drawSelf: (const NXRect *)rect inView: controlView
{
    center.x = NX_WIDTH(rect)/2.0;
    center.y = NX_HEIGHT(rect)/2.0;
	if (NX_WIDTH(rect) > NX_HEIGHT(rect))
    	radius = (NX_HEIGHT(rect)/2.0) - 8.0;
	else
		radius = (NX_WIDTH(rect)/2.0) - 8.0;

	// Redraws the face in the cached image.		
	[self drawFace: rect];
	// Draws the gauge itself. 
	[self drawInside: rect inView: controlView];
	return self;
}


- drawInside: (const NXRect *)rect inView: controlView
{
	// Composites the "face" of the gauge, then draws the hand.
	// This method will also work inside a flipped view (like a Matrix).
	NXPoint corner = {0.0, 0.0};

	// If view is flipped composite from lower left corner.
	if ([controlView isFlipped])
		corner.y = NX_HEIGHT (rect);

	// Composite the gauge face into the view.
	[cache composite: NX_SOVER toPoint: &corner];
	// Draw the hand.
	[self drawHand: rect flipped: [controlView isFlipped] ];	

	return self;
}


- drawHand: (const NXRect *)rect flipped: (BOOL)viewFlipped
{
	// If you want the gauge to have a different look you should only
	// have to override drawFace: and drawHand:. The passed rect
	// is the same rect that is passed to drawInside:.
    float valueAngle;	

    valueAngle = startAngle - degreesPerUnit*(value-minValue);
	
	if (viewFlipped == YES)
	{
		PSgsave();
		PSWflipme (NX_HEIGHT(rect));
	 }
	
	NXSetColor ([self textColor]);
    PSWdrawHand(center.x,center.y,valueAngle, radius*handRatio);

	if (viewFlipped == YES)
		PSgrestore();
	return self;
}

	
- drawFace: (const NXRect *)rect 
{	
	// This method draws the gauge face image in an NXImage.  It erases
	// the background, draws the circular border, draws the tick marks and 
	// labels them appropriately. Since we are drawing into an NXImage
	// we don't care whether the view we are compositing into is flipped or 
	// not.
    float angle, angleIncrement;
    int number;
    NXSize string;
    NXPoint pt;
    char numString[10];
    NXSize cacheSize;
	Font *cellsFont = [self font];
	Font *flippedFont;
	float titleY;
	
	cacheSize.width = NX_WIDTH (rect);
	cacheSize.height = NX_HEIGHT (rect);
	[cache setSize: &cacheSize];
	
	if ([cache lockFocus] == NO)
		return self;

	// Do the entire rect in our background color.
//	NXSetColor ([self backgroundColor]);
	PSsetalpha (0.0);
    NXRectFill(rect);
	PSsetalpha (1.0);

	// Draw the gauge face.
	NXSetColor ([self gaugeColor]);
    PSWdrawBorder(center.x, center.y, radius);
    angleIncrement = angleRange/((maxValue-minValue)/(float)tickInterval); 
	if (tickRatio > 0.0)
	{
		NXSetColor ([self textColor]);
    	PSWdrawTicks(center.x, center.y, radius*tickRatio, angleIncrement/2.0, 
						startAngle, startAngle+angleRange); 
	 }

	if ([self title] != NULL)
	{
		// Makes sure that the title font if using the NX_IDENTITYMATRIX.	 
		flippedFont = [Font newFont: [titleFont name] 
						size: [titleFont pointSize] matrix: NX_IDENTITYMATRIX];
		[flippedFont set];     
	
		// Spit out the title.
		string.height = [flippedFont pointSize];
		string.width = [flippedFont getWidthOf: [self title] ];
		if ([self titlePosition] == NX_ATTOP)
			titleY = center.y+(NX_HEIGHT(rect)/10.0);
		else
			titleY = center.y-(NX_HEIGHT(rect)/12.0)-string.height;
		
		NXSetColor ([self textColor]);	
		PSWdrawString((NX_WIDTH(rect)-string.width)/2, titleY, 
						[self title]);
	 }
	 
	// Make sure the cell's font (for the numbers) is also using
	// the NX_IDENTITYMATRIX.	 
	flippedFont = [Font newFont: [cellsFont name] 
					size: [cellsFont pointSize] matrix: NX_IDENTITYMATRIX];
	[flippedFont set];     
    string.height = [flippedFont pointSize];
    
	// Spit out all the numbers.
    number =  minValue;
	NXSetColor ([self textColor]);
    for(angle=startAngle;angle>=startAngle-angleRange;angle -= angleIncrement){
        sprintf(numString,"%d",number);
		string.width = [cellsFont getWidthOf: numString];
		pt.x = cos(angle/57.3)*(radius-1.0-string.width/2)+ center.x; 
		pt.y = sin(angle/57.3)*(radius-1.0-string.height/2) + center.y ;
		PSWdrawString(pt.x-(string.width/2), pt.y-(string.height/2.5),
			numString);
		number += tickInterval;
    }
    
    [cache unlockFocus]; 
    return self;
}	


- highlight:(const NXRect *)cellFrame inView:aView lit:(BOOL)flag
{
	// We don't want highlighting to occur.
	return self;
}


- awake
{
	[super awake];
	cache = [ [NXImage alloc] init];
	strValue = NULL;
    degreesPerUnit = angleRange/(maxValue-minValue);
	return self;
}


- read: (NXTypedStream *)stream
{
	int version;
	
	[super read: stream];
	version = NXTypedStreamClassVersion (stream, MISCGAUGECELL_NAME);
	switch (version)
	{
		case MISCGAUGECELL_VERSION:
			NXReadTypes (stream, "ffiff", &startAngle, &angleRange, 
							&tickInterval, &tickRatio, &handRatio);
			NXReadTypes (stream, "fffs", &minValue, &maxValue, &value,
							&titlePosition);
//			backgroundColor = NXReadColor (stream);
			gaugeColor = NXReadColor (stream);
			textColor = NXReadColor (stream);
			titleFont = NXReadObject (stream);
			break;
			
		default:
			NXLogError ("Could not read typed stream for class %s, version %d", MISCGAUGECELL_NAME, version);
			break;
	 }
	return self;
}


- write: (NXTypedStream *)stream
{
	[super write: stream];
	NXWriteTypes (stream, "ffiff", &startAngle, &angleRange, 
					&tickInterval, &tickRatio, &handRatio);
	NXWriteTypes (stream, "fffs", &minValue, &maxValue, &value,
					&titlePosition); 
//	NXWriteColor (stream, backgroundColor);
	NXWriteColor (stream, gaugeColor);
	NXWriteColor (stream, textColor);
	NXWriteObject (stream, titleFont);
	return self;
}

@end


/****************************************************************************
  CHANGES:
   Version 0.3, March 19, 1995
  1. Fixed so it would draw with a transparent background correctly.
   Version 0.4, March 21, 1995
  2. Removed the lastRect and needRedraw ivars since as long as 
     drawSelf:inView: is called only when needed the face needs 
	 redrawing (from GaugeView) then drawing will be fairly efficient.
  3. Added drawHand:flipped: so you could easily customize the look
     of the gauge face and hand.
	 
 ****************************************************************************/
 